package com.wondertek.service.service.sys.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wondertek.common.base.Result;
import com.wondertek.common.exception.AppException;
import com.wondertek.common.log.AutoLog;
import com.wondertek.common.log.AutoLogDto;
import com.wondertek.common.log.AutoLogService;
import com.wondertek.common.util.base.AddressUtils;
import com.wondertek.common.util.base.PoiUtil;
import com.wondertek.common.util.base.SpringUtil;
import com.wondertek.common.util.security.LoginUser;
import com.wondertek.common.util.security.SecurityUtil;
import com.wondertek.service.dto.app.req.AppLogPageReq;
import com.wondertek.service.dto.app.res.AppLogPageRes;
import com.wondertek.service.dto.sys.req.SysLogQueryReq;
import com.wondertek.service.dto.sys.res.SysLoginLogExport;
import com.wondertek.service.dto.sys.res.SysOperateLogExport;
import com.wondertek.service.entity.sys.SysLogPo;
import com.wondertek.service.mapper.sys.SysLogMapper;
import com.wondertek.service.service.sys.ISysLogService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author xqs
 */
@Service
@Slf4j
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLogPo>
    implements ISysLogService, AutoLogService {

    @Autowired
    private SysLogMapper sysLogMapper;

    @Override
    public Page<SysLogPo> page(SysLogQueryReq sysLogQueryReq) {
        LambdaQueryWrapper<SysLogPo> wrapper = new LambdaQueryWrapper<>();
        List<String> list = Lists.newArrayList();
        list.add("log.login");
        list.add("log.logout");
        switch (sysLogQueryReq.getType()){
            case 1:
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getSearchText())){
                    wrapper.and(i -> i.like(SysLogPo::getUsername, sysLogQueryReq.getSearchText())
                            .or().like(SysLogPo::getUserId, sysLogQueryReq.getSearchText())
                            .or().like(SysLogPo::getDeviceId, sysLogQueryReq.getSearchText())
                            .or().like(SysLogPo::getHandleType, sysLogQueryReq.getSearchText())
                    );
                }
                if(StrUtil.isNotBlank(sysLogQueryReq.getSiteId())){
                    wrapper.eq(SysLogPo::getSiteId,sysLogQueryReq.getSiteId());
                }
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getStartTime())){
                    wrapper.gt(SysLogPo::getHandleTime,sysLogQueryReq.getStartTime());
                }
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getEndTime())){
                    wrapper.lt(SysLogPo::getHandleTime,sysLogQueryReq.getEndTime());
                }
                wrapper.notIn(SysLogPo::getHandleType, list);
                wrapper.orderByDesc(SysLogPo::getHandleTime);
                wrapper.select(SysLogPo::getUsername,SysLogPo::getUserId,
                        SysLogPo::getHandleType, SysLogPo::getHandleTime,
                        SysLogPo::getSiteName, SysLogPo::getDeviceId, SysLogPo::getId);
                break;
            case 2:
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getSearchText())){
                    wrapper.and(i -> i.like(SysLogPo::getHandleIp, sysLogQueryReq.getSearchText())
                            .or().like(SysLogPo::getUserId, sysLogQueryReq.getSearchText())
                            .or().like(SysLogPo::getUsername, sysLogQueryReq.getSearchText())
                    );
                }
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getStartTime())){
                    wrapper.gt(SysLogPo::getHandleTime,sysLogQueryReq.getStartTime());
                }
                if(ObjectUtil.isNotEmpty(sysLogQueryReq.getEndTime())){
                    wrapper.lt(SysLogPo::getHandleTime,sysLogQueryReq.getEndTime());
                }
                wrapper.in(SysLogPo::getHandleType, list);
                wrapper.orderByDesc(SysLogPo::getHandleTime);
                wrapper.select(SysLogPo::getUsername,SysLogPo::getUserId, SysLogPo::getHandleTime,
                        SysLogPo::getHandleIp,SysLogPo::getId);
                break;
            default:
                break;
        }
        return this.page(new Page<>(sysLogQueryReq.getPageNo(), sysLogQueryReq.getPageSize()), wrapper);
    }

    @Override
    public void export(SysLogQueryReq sysLogQueryReq, HttpServletResponse response) {
        LambdaQueryWrapper<SysLogPo> wrapper = new LambdaQueryWrapper<>();
        List<String> list = Lists.newArrayList();
        list.add("log.login");
        list.add("log.logout");
        List<String> ids = sysLogQueryReq.getIds();
        List<SysLogPo> sysOperateLogPoList;
        switch (sysLogQueryReq.getType()){
            case 1:
                if(!CollUtil.isEmpty(ids)){
                    sysOperateLogPoList = sysLogMapper.selectBatchIds(ids);
                }else {
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getSearchText())) {
                        wrapper.and(i -> i.like(SysLogPo::getUsername, sysLogQueryReq.getSearchText())
                                .or().like(SysLogPo::getUserId, sysLogQueryReq.getSearchText())
                                .or().like(SysLogPo::getDeviceId, sysLogQueryReq.getSearchText())
                                .or().like(SysLogPo::getHandleType, sysLogQueryReq.getSearchText())
                        );
                    }
                    if (StrUtil.isNotBlank(sysLogQueryReq.getSiteId())) {
                        wrapper.eq(SysLogPo::getSiteId, sysLogQueryReq.getSiteId());
                    }
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getStartTime())) {
                        wrapper.gt(SysLogPo::getHandleTime, sysLogQueryReq.getStartTime());
                    }
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getEndTime())) {
                        wrapper.lt(SysLogPo::getHandleTime, sysLogQueryReq.getEndTime());
                    }
                    wrapper.notIn(SysLogPo::getHandleType, list);
                    wrapper.orderByDesc(SysLogPo::getHandleTime);
                    wrapper.select(SysLogPo::getUsername, SysLogPo::getUserId,
                            SysLogPo::getHandleType, SysLogPo::getHandleTime,
                            SysLogPo::getSiteName, SysLogPo::getDeviceId);
                    sysOperateLogPoList = this.list(wrapper);
                }
                if(CollUtil.isEmpty(sysOperateLogPoList)){
                    PoiUtil.writeExcelToResponse("操作记录", SysOperateLogExport.class, new ArrayList(),response);
                }else{
                    List<SysOperateLogExport> operateLogExportList = BeanUtil.copyToList(sysOperateLogPoList, SysOperateLogExport.class);
                    PoiUtil.writeExcelToResponse("操作记录", SysOperateLogExport.class, operateLogExportList, response);
                }
                break;
            case 2:
                if(!CollUtil.isEmpty(ids)){
                    sysOperateLogPoList = sysLogMapper.selectBatchIds(ids);
                }else {
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getSearchText())) {
                        wrapper.and(i -> i.like(SysLogPo::getHandleIp, sysLogQueryReq.getSearchText())
                                .or().like(SysLogPo::getUserId, sysLogQueryReq.getSearchText())
                                .or().like(SysLogPo::getUsername, sysLogQueryReq.getSearchText())
                        );
                    }
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getStartTime())) {
                        wrapper.gt(SysLogPo::getHandleTime, sysLogQueryReq.getStartTime());
                    }
                    if (ObjectUtil.isNotEmpty(sysLogQueryReq.getEndTime())) {
                        wrapper.lt(SysLogPo::getHandleTime, sysLogQueryReq.getEndTime());
                    }
                    wrapper.in(SysLogPo::getHandleType, list);
                    wrapper.orderByDesc(SysLogPo::getHandleTime);
                    wrapper.select(SysLogPo::getUsername, SysLogPo::getUserId, SysLogPo::getHandleTime,
                            SysLogPo::getHandleIp);
                    sysOperateLogPoList = this.list(wrapper);
                }
                if(CollUtil.isEmpty(sysOperateLogPoList)){
                    PoiUtil.writeExcelToResponse("登录日志", SysLoginLogExport.class, new ArrayList(),response);
                }else{
                    List<SysLoginLogExport> loginLogExportList = BeanUtil.copyToList(sysOperateLogPoList, SysLoginLogExport.class);
                    PoiUtil.writeExcelToResponse("登录日志", SysLoginLogExport.class, loginLogExportList, response);
                }
                break;
            default:
                break;
        }
    }

    @Override
    public Page<AppLogPageRes> appPage(AppLogPageReq appLogPageReq) {
        Page<AppLogPageRes> resultPage = new Page<>(appLogPageReq.getPageNo(), appLogPageReq.getPageSize());
        LambdaQueryWrapper<SysLogPo> wrapper = new LambdaQueryWrapper<>();
        List<String> list = Lists.newArrayList();
        list.add("log.login");
        list.add("log.logout");
        switch (appLogPageReq.getType()){
            case 1:
                wrapper.eq(SysLogPo::getUserId, SecurityUtil.getLoginUser().getUserId());
                wrapper.notIn(SysLogPo::getHandleType, list);
                wrapper.orderByDesc(SysLogPo::getHandleTime);
                wrapper.select(SysLogPo::getHandleType, SysLogPo::getHandleTime,
                        SysLogPo::getDeviceId);
                break;
            case 2:
                wrapper.eq(SysLogPo::getUserId, SecurityUtil.getLoginUser().getUserId());
                wrapper.in(SysLogPo::getHandleType, list);
                wrapper.orderByDesc(SysLogPo::getHandleTime);
                wrapper.select(SysLogPo::getHandleTime,
                        SysLogPo::getHandleType);
                break;
            default:
                break;
        }
        Page<SysLogPo> page = this.page(new Page<>(appLogPageReq.getPageNo(), appLogPageReq.getPageSize()), wrapper);
        if(CollUtil.isEmpty(page.getRecords())){
            return resultPage;
        }
        List<AppLogPageRes> appLogPageResList = BeanUtil.copyToList(page.getRecords(),AppLogPageRes.class);
        resultPage.setRecords(appLogPageResList);
        resultPage.setTotal(page.getTotal());
        return resultPage;
    }

    @Async("threadPoolTaskExecutor")
    @Override
    public void asyncPoressLog(ProceedingJoinPoint point, Object result, long startTime, String ip, LoginUser loginUser, Throwable tr) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Object target = point.getTarget();
        Object[] args = point.getArgs();
        String params = "";
        try {
            params = JSONUtil.toJsonStr(args);
        } catch (Exception e) {
            log.error("parse jsonStr err {}",e.getMessage(),e);
        }
        AutoLog autoLog = method.getAnnotation(AutoLog.class);
        String handleType = autoLog.handleType();
        String handleNote = autoLog.handleNote();
        handleNote = SpringUtil.spelParse(handleNote, method, args);
        String category = autoLog.category();

        AutoLogDto autoLogDto = new AutoLogDto();
        autoLogDto.setCategory(category);
        autoLogDto.setHandleType(handleType);
        autoLogDto.setHandleNote(handleNote);
        autoLogDto.setHandleIp(ip);
        autoLogDto.setHandleTime(new Date());
        autoLogDto.setParams(toJsonStr(args));
        autoLogDto.setUserId(loginUser.getUserId());
        autoLogDto.setUsername(loginUser.getUsername());
        autoLogDto.setUserInfo(loginUser.getUserInfo());
        autoLogDto.setCostTime(System.currentTimeMillis() - startTime);
        autoLogDto.setAddress(AddressUtils.getRealAddressByIP(ip));
        autoLogDto.setTraceId(MDC.get("traceId"));
        if (result instanceof Result) {
            autoLogDto.setCode(((Result<?>) result).getCode());
            autoLogDto.setSuccess(((Result<?>) result).getSuccess());
            autoLogDto.setMessage(((Result<?>) result).getMessage());
        }else {
            autoLogDto.setCode(HttpStatus.HTTP_OK);
            autoLogDto.setSuccess(true);
            autoLogDto.setMessage("操作成功!");
        }
        if (tr != null) {
            String stacktrace = ExceptionUtil.stacktraceToString(tr);
            autoLogDto.setStacktrace(stacktrace);
            autoLogDto.setMessage(tr.getMessage());
            autoLogDto.setSuccess(false);
            if (tr instanceof AppException) {
                autoLogDto.setCode(((AppException) tr).getCode());
            } else {
                autoLogDto.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
            }
        }
        this.saveAutoLog(autoLogDto);
    }

    private String toJsonStr(Object[] args){
        Map<String,Object> map = new HashMap<>();
        for(int i=0; i< args.length; i++){
            Object arg = args[i];
            if(arg instanceof MultipartFile
                    || arg instanceof HttpServletRequest
                    || arg instanceof HttpServletRequest){
                continue;
            }
            try {
                String jsonStr = JSONUtil.toJsonStr(arg);
                map.put("arg"+i,JSONUtil.isTypeJSON(jsonStr)?JSONUtil.parse(jsonStr):jsonStr);
            }catch (Exception ex){
                log.error("parse jsonStr err {}",ex.getMessage(),ex);
            }
        }
        return JSONUtil.toJsonStr(map);
    }

    @Override
    public void saveAutoLog(AutoLogDto autoLogDto) {
        SysLogPo sysLogPo = new SysLogPo();
        BeanUtil.copyProperties(autoLogDto,sysLogPo);
        sysLogPo.setRoleName((String) autoLogDto.getUserInfo().get("roleNames"));
        this.save(sysLogPo);
    }
}
